class PocketCube:
    """

        该方法比较抽象。

        魔方有六个面，面索引称为 direction 为 F B U D L R
        魔方每个面有四个色块，从左上角顺时针定义索引-faceIndex 为 0 1 2 3
        魔方每个面有四个边界，从上边界顺时针定义索引-edgeIndex 为 0 1 2 3
        魔方的每个面展开成一个平面，这样就可以设置面和面之间 边界的绑定关系

            面展开如下：
                  B
                L U R D
                  F

            边界关系：
                          0
                        3 B 1
                          2

                  0       0       0       0
                3 L 1   3 U 1   3 R 1   3 D 1
                  2       2       2       2

                          0
                        3 F 1
                          2

        以上设定了解清楚之后，后面就简单了
        例如接收到direction面要顺时针旋转
        1、get_edgeIndex_from_direction 找相关面边界
        2、get_faceIndex_from_edgeIndex 找到direction面四周的八个面，形成一个列表 - faces_to_rotate
        3、你会发现，这八个面 - faces_to_rotate 就是顺时针排列的，你只需要把他们shift一下就能完成旋转
        4、至于direction面的色块，更是shift一下就行
        5、如果要逆时针旋转，只需要把 faces_to_rotate列表 给 .reverse() 就可以

    """

    @staticmethod  # 根据一个边界索引，查找和边界接触的色块索引
    def get_faceIndex_from_edgeIndex(edgeIndex):
        edges = [(1, 0), (2, 1), (3, 2), (0, 3)]
        return edges[edgeIndex]

    @staticmethod  # 根据魔方的面索引，查找该面的边界索引 所一一对应的 其它面的边界索引
    def get_edgeIndex_from_direction(direction):
        directions = {
            "F": [("U", 2), ("R", 2), ("D", 2), ("L", 2)],
            "B": [("D", 0), ("R", 0), ("U", 0), ("L", 0)],
            "U": [("B", 2), ("R", 3), ("F", 0), ("L", 1)],
            "D": [("B", 0), ("L", 3), ("F", 2), ("R", 1)],
            "L": [("B", 3), ("U", 3), ("F", 3), ("D", 1)],
            "R": [("B", 1), ("D", 3), ("F", 1), ("U", 1)],
        }
        return directions[direction]

    def __init__(self):
        self.faces = {  # 直接用列表会不会快一点
            "F": ["R"]*4,
            "B": ["O"]*4,
            "U": ["W"]*4,
            "D": ["Y"]*4,
            "L": ["G"]*4,
            "R": ["B"]*4,
        }

    def get_color_by_faceID(self, direction, faceIndex):
        return self.faces[direction][faceIndex]

    def set_color_by_faceID(self, direction, faceIndex, color: str):
        self.faces[direction][faceIndex] = color
        # print(f"sat {color} to {direction}-{faceIndex}")

    def rotate_faces_around(self, faces_to_rotate: list, clockwise: bool):
        if clockwise:
            faces_to_rotate.reverse()

        firstFacesColors = [self.get_color_by_faceID(d, f) for d, f in faces_to_rotate[0]]

        for i in range(3):
            tColors = [self.get_color_by_faceID(d, f) for d, f in faces_to_rotate[i+1]]
            for (d, f), tColor in zip(faces_to_rotate[i], tColors):
                self.set_color_by_faceID(d, f, tColor)

        for (d, f), tColor in zip(faces_to_rotate[3], firstFacesColors):
            self.set_color_by_faceID(d, f, tColor)

    def rotate_face(self, direction: str, clockwise: bool):
        if clockwise:
            newFaces = [self.faces[direction][3]] + self.faces[direction][:3]
        else:
            newFaces = self.faces[direction][1:] + [self.faces[direction][0]]

        self.faces[direction] = newFaces

    def rotate(self, direction: str, clockwise=True, times=1) -> None:
        """
        名称      公式符号   direction参数
        Front    F         F
        Back     B         B
        Up       U         U
        Down     D         D
        Left     L         L
        Right    R         R
        """

        for _ in range(times):

            edges = self.get_edgeIndex_from_direction(direction)
            faces_to_rotate = []
            for faceName, edgeIndex in edges:
                faceIndexes = self.get_faceIndex_from_edgeIndex(edgeIndex)
                faces_to_rotate.append(
                    [(faceName, faceIndex) for faceIndex in faceIndexes]
                )

            # print(faces_to_rotate)
            self.rotate_faces_around(faces_to_rotate, clockwise)
            self.rotate_face(direction, clockwise)

    def show_front_up_right_view(self) -> str:
        """
        ▇ ▇ ▇ ▇ ▇ ▇ ▇         ▇ ▇ ▇ A ▇ ▇ ▇               W                     W
        ▇ ▇ ▇ ▇ ▇ ▇ ▇         ▇ B ▇ ▇ ▇ C ▇           W       W             W  上面  W
        ▇ ▇ ▇ ▇ ▇ ▇ ▇         D ▇ ▇ E ▇ ▇ F         R     W     B         R     W     B
        ▇ ▇ ▇ ▇ ▇ ▇ ▇         ▇ ▇ G ▇ H ▇ ▇             R   B             正面 R   B 右面
        ▇ ▇ ▇ ▇ ▇ ▇ ▇         I ▇ ▇ ▇ ▇ ▇ J         R           B         R           B
        ▇ ▇ ▇ ▇ ▇ ▇ ▇         ▇ ▇ K ▇ L ▇ ▇             R   B                 R   B

        假设一个7*6的网格        形成字符位置模板        用颜色首字母填充模板           很简单吧~
        """

        template = \
            "      {}      \n  {}       {}  \n{}     {}     {}\n    {}   {}    \n{}           {}\n    {}   {}    \n"
        #          A           B        C     D      E      F       G    H       I            J       K    L

        faces = [
            ("U", 0), ("U", 3), ("U", 1), ("F", 0),
            ("U", 2), ("R", 0), ("F", 1), ("R", 3),
            ("F", 3), ("R", 1), ("F", 2), ("R", 2),
        ]

        colors = [
            self.get_color_by_faceID(*face)
            for face in faces
        ]

        # 模板字符串用 .format 方法进行填充
        filled = template.format(*colors)

        return filled

    def act_formula(self, formula: str) -> None:

        actions = list("FBUDLR")

        cursor = 0
        prev_config = [None, True, 1]

        try:
            while cursor < len(formula):
                char = formula[cursor]

                if char == "'":
                    prev_config[1] = not prev_config[1]

                if char.isdigit():
                    prev_config[2] = int(char)

                if char in actions:
                    if prev_config[0] is not None:
                        self.rotate(*prev_config)

                    prev_config = [char, True, 1]

                if cursor == len(formula) - 1:
                    self.rotate(*prev_config)

                cursor += 1
        except:
            raise Exception("公式错误")


class CaseTester:
    """
    这是测试类，在主程序调用 CaseTester.test(放入你完成的二阶魔方类) 即可测试。

    请注意，您的魔方颜色的初始化一定要和测试案例相同，遵循以下规则：

    Front   正面    (R)Red
    Back    背面    (O)Orange
    Up      上面    (W)White
    Down    下面    (Y)Yellow
    Left    左面    (G)Green
    Right   右面    (B)Blue

    """

    TestCases = {
        "case1": {
            "input": "UR'2U'L2",
            "output": "      Y      \n  Y       Y  \nO     W     O\n    R   B    \nB           O\n    G   O    \n"
        },
        "case2": {
            "input": "D2LB'FL'R",
            "output": "      W      \n  O       W  \nG     R     O\n    B   W    \nG           R\n    B   W    \n"
        },
        "case3": {
            "input": "FB2U'F2DU",
            "output": "      B      \n  B       Y  \nY     G     G\n    R   W    \nB           B\n    R   Y    \n"
        },
        "case4": {
            "input": "FB2D'RFD'ULR",
            "output": "      B      \n  B       W  \nR     G     G\n    O   Y    \nY           O\n    B   Y    \n"
        },
        "case5": {
            "input": "D'L'R2FB'U'DF2U'",
            "output": "      R      \n  G       B  \nW     B     W\n    O   W    \nG           G\n    O   W    \n"
        },
        "case6": {
            "input": "D'R2FUF'D'L2FUD",
            "output": "      B      \n  W       W  \nR     W     B\n    O   G    \nY           G\n    W   B    \n"
        },
    }

    @classmethod
    def test(cls, PocketCube: type):

        if not type(PocketCube) == type:
            raise TypeError("类型错误，请放入PocketCube类。")

        import time

        TimeUses = []
        correct_times = 0
        for caseName, body in cls.TestCases.items():

            print(f"测试用例 - {caseName}  -----------------------")

            start_time = time.perf_counter()

            pocketCube = PocketCube()
            pocketCube.act_formula(body['input'])
            yourAns = pocketCube.show_front_up_right_view()

            end_time = time.perf_counter()

            print(f"你的答案\n\n{yourAns}\n")

            if yourAns == body['output']:
                print("正确！！！！")
                correct_times += 1
            else:
                print("错误！！！！")
                print(f"正确答案\n\n{body['output']}\n")

            elapsed_time = end_time - start_time
            TimeUses.append(elapsed_time)

            print(f"用时：{elapsed_time:.6f} 秒\n")

        print(f"{'-' * 80}\n平均用时：{sum(TimeUses) / len(TimeUses):.6f} 秒")
        print(f"正确率：{correct_times / len(cls.TestCases) * 100} %")


if __name__ == '__main__':

    # 实例化你的二阶魔方
    # pocketCube = PocketCube()


    # # 执行一段魔方公式
    # pocketCube.act_formula("U2") # "UR'2UL'2"
    #
    # # 查看魔方结果
    # print(pocketCube.show_front_up_right_view())

    # 调用测试类进行测试

    # CaseTester.test(PocketCube)

    import time

    start_time = time.perf_counter()
    pocketCube = PocketCube()
    pocketCube.act_formula("UR'2U'L2D2LB'FL'RFB2U'F2DUFB2D'RFD'ULRD'L'R2FB'U'DF2U'D'R2FUF'D'L2FUD")
    ans = pocketCube.show_front_up_right_view()
    end_time = time.perf_counter()
    elapsed_time = end_time - start_time

    print(ans)
    print(f"用时 {elapsed_time:.6f} 秒")
